feat: Add JWT cookie-based authentication#167
Open
devin-ai-integration[bot] wants to merge 2 commits intoDevOpsfrom
Open
feat: Add JWT cookie-based authentication#167devin-ai-integration[bot] wants to merge 2 commits intoDevOpsfrom
devin-ai-integration[bot] wants to merge 2 commits intoDevOpsfrom
Conversation
- Add jjwt-api, jjwt-impl, jjwt-jackson dependencies (0.12.5) - Add JWT secret (via env var) and expiration config in application.properties - Create JwtUtil for token generation, extraction, and validation - Create JwtAuthenticationFilter to authenticate requests via JWT cookie - Create AuthController handling login/logout with JWT cookies - Update SecurityConfig to stateless sessions with JWT filter - Remove /login mapping from BankController (moved to AuthController) Co-Authored-By: Ivan Sobina <sobina.ivan@gmail.com>
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…on logout Spring Security's built-in LogoutFilter was intercepting /logout requests before the AuthController, preventing the JWT cookie from being cleared. Fix disables the default LogoutFilter and uses ResponseEntity for explicit control over the Set-Cookie header in the logout response. Co-Authored-By: Ivan Sobina <sobina.ivan@gmail.com>
Author
End-to-End Test Results — JWT AuthenticationRan locally against MySQL Docker + JWT_SECRET. All 7 tests passed. Test Results (7/7 passed)
JWT Cookie Verification (curl)Bug Found & FixedSpring Security's built-in |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces Spring Security's built-in form login and session-based authentication with stateless JWT cookie-based authentication using jjwt 0.12.5.
New files:
JwtUtil– generates, parses, and validates HMAC-SHA256 JWTs. Secret is read from${JWT_SECRET}env var (must be base64-encoded 256-bit key).JwtAuthenticationFilter–OncePerRequestFilterthat reads ajwtcookie, validates it, and populatesSecurityContextHolder.AuthController– handlesGET/POST /loginandGET /logout, issuing/clearing the JWT cookie.Modified files:
SecurityConfig– sessions set toSTATELESS,formLogin/logoutblocks removed, Spring Security's defaultLogoutFilterexplicitly disabled, JWT filter inserted beforeUsernamePasswordAuthenticationFilter, unauthenticated requests redirect to/login.BankController–@GetMapping("/login")removed (moved toAuthController).application.properties– addedjwt.secret=${JWT_SECRET}andjwt.expiration-ms=3600000.pom.xml– addedjjwt-api,jjwt-impl,jjwt-jackson0.12.5.Updates since last revision
LogoutFilterwas silently interceptingGET /logoutbefore theAuthControllercould run, so the JWT cookie was never cleared. Fix: added.logout(logout -> logout.disable())inSecurityConfigand switched the logout endpoint to return aResponseEntitywith an explicitSet-Cookieheader viaResponseCookie, ensuring the cookie is reliably cleared on the redirect response./logoutto thepermitAll()matcher list.Review & Testing Checklist for Human
Secureflag missing on JWT cookie –AuthControllersetsHttpOnlybut never callscookie.setSecure(true)(login) or setssecure(true)(logoutResponseCookie). The cookie will be sent over plain HTTP. Verify whether HTTPS-only enforcement is acceptable and add theSecureflag if so.SameSite=Stricton the cookie or re-enabling CSRF protection.JWT_SECRETenv var required at startup –JwtUtildoesBase64.getDecoder().decode(secret), so if the env var is unset or not valid base64, the app will crash on boot. Ensure all deployment environments (Docker Compose, K8s ConfigMap/Secret, local dev) have this variable configured.JwtAuthenticationFiltercallsaccountService.loadUserByUsername()for each authenticated request, which may negate the performance benefit of stateless JWT.ResponseEntitywith explicit headers while login usesresponse.addCookie()+ Spring MVC redirect. This was necessary because Spring MVC's redirect view was not preserving theSet-Cookieheader reliably. Verify both paths work in your deployment environment.Suggested test plan:
JWT_SECRETto a base64-encoded 256-bit key and start the app.GET /login→ login page renders.POST /loginwith valid creds → response setsjwtHttpOnly cookie, redirects to/dashboard.GET /dashboardwith cookie → authenticated view with user data.GET /dashboardwithout cookie → redirects to/login.GET /logout→ cookie cleared (Set-Cookie: jwt=; Max-Age=0), redirects to/login?logout.GET /dashboardafter logout → redirects to/login(cookie is gone).POST /register→ still works and redirects to/login.Notes
validateTokenmethod uses a broadcatch (Exception e)which will swallow non-JWT errors silently.LogoutFiltermust be disabled (.logout(logout -> logout.disable())) for custom JWT cookie logout to work. Without this, the filter intercepts/logoutand performs its own redirect, discarding the cookie-clearing header.Link to Devin session: https://app.devin.ai/sessions/6a04dcd6afda41a1a45265e1d3d4b9c3
Requested by: @isobina